1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.hiprenderer.shader.shader; 12 public import hip.api.renderer.shadervar : ShaderVariablesLayout, ShaderVar; 13 public import hip.api.renderer.shadervar; 14 15 import hip.api.data.commons:IReloadable; 16 import hip.api.renderer.texture; 17 import hip.math.matrix; 18 import hip.error.handler; 19 import hip.api.renderer.shadervar; 20 import hip.hiprenderer.shader; 21 import hip.hiprenderer.renderer; 22 import hip.util.file; 23 import hip.util.string:indexOf; 24 public import hip.api.renderer.shader; 25 26 27 28 29 30 private __gshared ShaderProgram lastBoundShader; 31 32 public class Shader : IReloadable 33 { 34 VertexShader vertexShader; 35 FragmentShader fragmentShader; 36 ShaderProgram shaderProgram; 37 ShaderVariablesLayout[string] layouts; 38 protected ShaderVariablesLayout defaultLayout; 39 //Optional 40 IShader shaderImpl; 41 string fragmentShaderPath; 42 string vertexShaderPath; 43 44 protected string internalVertexSource; 45 protected string internalFragmentSource; 46 private bool isUseCall = false; 47 48 this(IShader shaderImpl) 49 { 50 this.shaderImpl = shaderImpl; 51 vertexShader = shaderImpl.createVertexShader(); 52 fragmentShader = shaderImpl.createFragmentShader(); 53 shaderProgram = shaderImpl.createShaderProgram(); 54 } 55 this(IShader shaderImpl, string vertexSource, string fragmentSource) 56 { 57 this(shaderImpl); 58 ShaderStatus status = loadShaders(vertexSource, fragmentSource); 59 if(status != ShaderStatus.SUCCESS) 60 { 61 import hip.console.log; 62 logln("Failed loading shaders"); 63 } 64 } 65 66 ShaderStatus loadShaders(string vertexShaderSource, string fragmentShaderSource, string shaderPath = "") 67 { 68 this.internalVertexSource = vertexShaderSource; 69 this.internalFragmentSource = fragmentShaderSource; 70 71 vertexShaderPath = fragmentShaderPath = shaderProgram.name = shaderPath; 72 if(!shaderImpl.compileShader(vertexShader, vertexShaderSource)) 73 return ShaderStatus.VERTEX_COMPILATION_ERROR; 74 if(!shaderImpl.compileShader(fragmentShader, fragmentShaderSource)) 75 return ShaderStatus.FRAGMENT_COMPILATION_ERROR; 76 if(!shaderImpl.linkProgram(shaderProgram, vertexShader, fragmentShader)) 77 return ShaderStatus.LINK_ERROR; 78 // deleteShaders(); 79 return ShaderStatus.SUCCESS; 80 } 81 82 ShaderStatus loadShadersFromFiles(string vertexShaderPath, string fragmentShaderPath) 83 { 84 this.vertexShaderPath = vertexShaderPath; 85 this.fragmentShaderPath = fragmentShaderPath; 86 return loadShaders(getFileContent(vertexShaderPath), getFileContent(fragmentShaderPath)); 87 } 88 89 ShaderStatus reloadShaders() 90 { 91 return loadShadersFromFiles(this.vertexShaderPath, this.fragmentShaderPath); 92 } 93 94 void setVertexAttribute(uint layoutIndex, int valueAmount, uint dataType, bool normalize, uint stride, int offset) 95 { 96 shaderImpl.sendVertexAttribute(layoutIndex, valueAmount, dataType, normalize, stride, offset); 97 } 98 99 100 /** 101 * If validateData is true, it will compare if the data has changed for choosing whether it should or not 102 send to the GPU. 103 * Params: 104 * name = 105 * val = 106 * validateData = 107 */ 108 public void setVertexVar(T)(string name, T val, bool validateData = false) 109 { 110 ShaderVar* v = tryGetShaderVar(name, ShaderTypes.vertex); 111 if(v != null) 112 { 113 v.set(val, validateData); 114 } 115 } 116 117 private ShaderVar* tryGetShaderVar(string name, ShaderTypes type) 118 { 119 import hip.util.conv:to; 120 bool isUnused; 121 ShaderVar* v = findByName(name, isUnused); 122 123 if(v is null) 124 { 125 if(!isUnused) 126 ErrorHandler.showWarningMessage("Shader " ~ type.to!string ~ " Var not set on shader loaded from '"~fragmentShaderPath~"'", 127 "Could not find shader var with name "~name~ 128 ((layouts.length == 0) ?". Did you forget to addVarLayout on the shader?" : 129 " Did you forget to add a layout namespace to the var name?") 130 ); 131 return null; 132 } 133 if(v.shaderType != type) 134 { 135 import hip.console.log; 136 logln = v.shaderType; 137 ErrorHandler.assertExit(false, "Variable named "~name~" must be from " ~ type.to!string ~ " Shader"); 138 } 139 return v; 140 } 141 /** 142 * If validateData is true, it will compare if the data has changed for choosing whether it should or not 143 send to the GPU. 144 * Params: 145 * name = 146 * val = 147 * validateData = 148 */ 149 public void setFragmentVar(T)(string name, T val, bool validateData = false) 150 { 151 ShaderVar* v = tryGetShaderVar(name, ShaderTypes.fragment); 152 if(v != null) 153 { 154 if(v.isBlackboxed) 155 { 156 if(shaderImpl.setShaderVar(v,shaderProgram, cast(void*)&val)) 157 v.isDirty = true; 158 } 159 else 160 v.set(val, validateData); 161 } 162 } 163 164 public void setFragmentVar(T)(ShaderVar* v, T val, bool validateData = false) 165 { 166 if(v.isBlackboxed) 167 { 168 if(shaderImpl.setShaderVar(v,shaderProgram, cast(void*)&val)) 169 v.isDirty = true; 170 } 171 else 172 v.set(val, validateData); 173 } 174 175 protected ShaderVar* findByName(string name, out bool isUnused) @nogc 176 { 177 int accessorSeparatorIndex = name.indexOf("."); 178 179 bool isDefault = accessorSeparatorIndex == -1; 180 if(isDefault) 181 { 182 ShaderVarLayout* sL = name in defaultLayout.variables; 183 if(sL !is null) 184 return sL.sVar; 185 isUnused = defaultLayout.isUnused(name); 186 } 187 else 188 { 189 ShaderVariablesLayout* l = (name[0..accessorSeparatorIndex] in layouts); 190 if(l !is null) 191 { 192 ShaderVarLayout* sL = name[accessorSeparatorIndex+1..$] in l.variables; 193 if(sL !is null) 194 return sL.sVar; 195 isUnused = l.isUnused(name[accessorSeparatorIndex+1..$]); 196 } 197 } 198 return null; 199 } 200 201 /** 202 * This function is mostly used for debug information 203 * Returns: The Variable names. 204 */ 205 private string[] getExistingVariableNames(ShaderTypes type) const 206 { 207 string[] ret; 208 foreach(k, v; layouts) 209 { 210 if(v.shaderType == type) 211 { 212 ret~= v.variables.keys; 213 } 214 } 215 return ret; 216 } 217 218 /** 219 * Use that instead of setVertexVar or setFragmentVar if you wish more performance. 220 * Params: 221 * name = The name of the variable 222 * Returns: The Shader Variable reference 223 */ 224 public ShaderVar* get(string name, ShaderTypes type) 225 { 226 ShaderVar* ret = tryGetShaderVar(name, type); 227 if(!ret) 228 { 229 import hip.util.string:join; 230 import hip.util.conv:to; 231 throw new Exception( 232 "Could not find variable named '"~name~"'.\n\tDefault Layout: ["~this.defaultLayout.name~ 233 "].\n\tShader Path: "~ (type == ShaderTypes.fragment ? fragmentShaderPath : vertexShaderPath) ~ 234 "\\tnExisting Variables in shader type "~type.to!string~":\n\t "~getExistingVariableNames(type).join("\n\t ") 235 ); 236 } 237 return ret; 238 } 239 240 241 public void addVarLayout(ShaderVariablesLayout layout) 242 { 243 ErrorHandler.assertLazyExit((layout.name in layouts) is null, "Shader: VariablesLayout '"~layout.name~"' is already defined"); 244 if(defaultLayout is null) 245 defaultLayout = layout; 246 layouts[layout.name] = layout; 247 layout.lock(this.shaderImpl); 248 shaderImpl.createVariablesBlock(layout, shaderProgram); 249 } 250 251 /** 252 * This creates a state in the current shader to which block will be accessed 253 * when using setVertexVar(".property"). If no default block is set ("") 254 * .property will always access the first block defined 255 * Params: 256 * blockName = Which block will be accessed with .property 257 */ 258 public void setDefaultBlock(string blockName){defaultLayout = layouts[blockName];} 259 260 void bind() 261 { 262 static if(UseDelayedUnbinding) 263 { 264 if(lastBoundShader is shaderProgram) 265 return; 266 if(lastBoundShader !is null) 267 shaderImpl.unbind(lastBoundShader); 268 lastBoundShader = shaderProgram; 269 } 270 shaderImpl.bind(shaderProgram); 271 } 272 void unbind() 273 { 274 static if(UseDelayedUnbinding) 275 return; 276 shaderImpl.unbind(shaderProgram); 277 } 278 void setBlending(HipBlendFunction src, HipBlendFunction dest, HipBlendEquation eq) 279 { 280 shaderImpl.setBlending(shaderProgram, src, dest, eq); 281 } 282 283 auto opDispatch(string member)() 284 { 285 static if(member == "useLayout") 286 { 287 isUseCall = true; 288 return this; 289 } 290 else 291 { 292 if(isUseCall) 293 { 294 setDefaultBlock(member); 295 isUseCall = false; 296 ShaderVar s; 297 return s; 298 } 299 return *defaultLayout.variables[member].sVar; 300 } 301 } 302 auto opDispatch(string member, T)(T value) 303 { 304 if(!defaultLayout.variables[member].sVar.set(value, false)) 305 { 306 ErrorHandler.assertExit(false, "Invalid value of type "~ 307 T.stringof~" passed to "~defaultLayout.name~"."~member); 308 } 309 } 310 311 void sendVars() 312 { 313 foreach(string key, ShaderVariablesLayout value; layouts) 314 { 315 if(!value.isDirty) 316 continue; 317 foreach(ref ShaderVarLayout varLayout; value.variables) 318 { 319 if(varLayout.sVar.isDirty) 320 { 321 if(varLayout.sVar.type == UniformType.floating3x3) 322 varLayout.sVar.set(HipRenderer.getMatrix(varLayout.sVar.get!Matrix3), false); 323 else if(varLayout.sVar.type == UniformType.floating4x4) 324 varLayout.sVar.set(HipRenderer.getMatrix(varLayout.sVar.get!Matrix4), false); 325 if(varLayout.sVar.usesMaxTextures) 326 varLayout.sVar.set(HipRenderer.getMaxSupportedShaderTextures(), true); 327 } 328 } 329 } 330 shaderImpl.sendVars(shaderProgram, layouts); 331 } 332 333 /** 334 * Bind array of textures. 335 * - This is handled a little different than simply blindly binding * to each slot. 336 * Since OpenGL, Direct3D and Metal handles that differently, a more general solution is required. 337 */ 338 void bindArrayOfTextures(IHipTexture[] textures, string varName) 339 { 340 shaderImpl.bindArrayOfTextures(shaderProgram, textures, varName); 341 } 342 343 344 protected void deleteShaders() 345 { 346 shaderImpl.deleteShader(&fragmentShader); 347 shaderImpl.deleteShader(&vertexShader); 348 } 349 350 bool reload() 351 { 352 vertexShader = shaderImpl.createVertexShader(); 353 fragmentShader = shaderImpl.createFragmentShader(); 354 shaderProgram = shaderImpl.createShaderProgram(); 355 356 return loadShaders(internalVertexSource, internalFragmentSource) == ShaderStatus.SUCCESS; 357 } 358 359 void onRenderFrameEnd() 360 { 361 shaderImpl.onRenderFrameEnd(shaderProgram); 362 } 363 364 }